#include <gtest/gtest.h>
#include <iostream>
#include <vtkm/cont/Algorithm.h>
#include <vtkm/exec/FunctorBase.h>

#include <vtkm/cont/ErrorUserAbort.h>
#include <vtkm/cont/RuntimeDeviceTracker.h>
#include <vtkm/cont/serial/internal/DeviceAdapterTagSerial.h>
#include <vtkm/filter/contour/Contour.h>
#include <vtkm/source/Wavelet.h>
#include <vtkm/cont/Token.h>

class ScheduleFunctor : public vtkm::exec::FunctorBase
{
public:
  ScheduleFunctor() {}
  ~ScheduleFunctor() {}

  VTKM_EXEC
  void operator()(vtkm::Id& index) const { EXPECT_TRUE(true); }

private:
};


bool ShouldAbort()
{
  static int abortCheckCounter = 0;
  static constexpr int abortAt = 5;
  if (++abortCheckCounter >= abortAt)
  {
    std::cout << "Abort check " << abortCheckCounter << ": true\n";
    return true;
  }

  std::cout << "Abort check " << abortCheckCounter << ": false\n";
  return false;
}

int TestAbort()
{
  vtkm::source::Wavelet wavelet;
  wavelet.SetExtent(vtkm::Id3(-15), vtkm::Id3(16));
  auto input = wavelet.Execute();

  auto range = input.GetField("RTData").GetRange().ReadPortal().Get(0);
  std::vector<vtkm::Float64> isovals;
  static constexpr int numDivs = 5;
  for (int i = 1; i < numDivs - 1; ++i)
  {
    auto v = range.Min +
      (static_cast<vtkm::Float64>(i) *
       ((range.Max - range.Min) / static_cast<vtkm::Float64>(numDivs)));
    isovals.push_back(v);
  }

  vtkm::filter::contour::Contour contour;
  contour.SetActiveField("RTData");
  contour.SetIsoValues(isovals);

  // First we will run the filter with the abort function set
  std::cout << "Run #1 with the abort function set\n";
  try
  {
    //vtkm::cont::ScopedRuntimeDeviceTracker tracker(ShouldAbort);

    auto result = contour.Execute(input);

    std::cout << "Error: filter execution was not aborted. Result: " << std::endl;
    // execution shouldn't reach here
    //print("Error: filter execution was not aborted. Result: ",
    //               result.GetNumberOfPoints(),
    //               " points and ",
    //               result.GetNumberOfCells(),
    //               " triangles");
  }
  catch (const vtkm::cont::ErrorUserAbort&)
  {
    std::cout << "Execution was successfully aborted\n";
  }

  // Now run the filter without the abort function
  std::cout << "Run #2 without the abort function set\n";
  try
  {
    auto result = contour.Execute(input);
    std::cout << "Success: filter execution was not aborted. Result: " << result.GetNumberOfPoints()
              << " points and " << result.GetNumberOfCells() << " triangles\n";
  }
  catch (const vtkm::cont::ErrorUserAbort&)
  {
    std::cout << "Execution was unexpectedly aborted" << std::endl;

    //print("Execution was unexpectedly aborted");
  }

  return 0;
}


TEST(Schedule, Functor)
{
  vtkm::cont::Algorithm::Schedule(ScheduleFunctor{}, 1);
  vtkm::cont::ArrayHandle<int> arr;
  arr.Allocate(12);
  auto portal = arr.WritePortal();

  using DevTag = vtkm::cont::DeviceAdapterTagSerial;
  vtkm::cont::Token token;
  arr.PrepareForInput(DevTag{}, token);
  //token.DetachFromAll();

  try
  {
    arr.Allocate(12);
  }
  catch (const vtkm::cont::Error& e)
  {
    EXPECT_TRUE(false);
    std::cout << e.what();
  }
  vtkm::cont::TryExecute(
    [&](auto device)
    {
      []() {};

      return true;
    });

  //TestAbort();
}
